import logging
import os
from os import path
import iso3166
from unidecode import unidecode

import static_babel_content
import utils
from utils import customize_babel_locale
from website.yaml_file import YamlFile
from safe_format import safe_format

logger = logging.getLogger(__name__)

COUNTRIES = static_babel_content.COUNTRIES


def _get_friendly_sorted_countries():
    friendly_countries = []
    for code, locale_name in COUNTRIES.items():
        locale_name_starts_with_ascii = locale_name[0].isascii()
        iso3166_english_name_normalized = unidecode(iso3166.countries.get(code).name).lower()
        locale_name_normalized = unidecode(locale_name).lower()
        if locale_name_starts_with_ascii or iso3166_english_name_normalized == locale_name_normalized:
            friendly_countries.append((code, locale_name_normalized, locale_name))
        else:
            friendly_countries.append((code, iso3166_english_name_normalized, locale_name))

    friendly_sorted_countries = sorted(friendly_countries, key=lambda c: c[1])

    return list(map(lambda c: ((c[0], c[2])), friendly_sorted_countries))


FRIENDLY_SORTED_COUNTRIES = _get_friendly_sorted_countries()

MAX_LEVEL = 16

# Define dictionary for available languages. Fill dynamically later.
ALL_LANGUAGES = {}
ALL_KEYWORD_LANGUAGES = {}

# Babel has a different naming convention than Weblate and doesn't support some languages -> fix this manually
# Map our langauge code to the language code recognized by Babel, or 'en' if Babel doesn't support this locale
# at all.
CUSTOM_BABEL_LANGUAGES = {'pa_PK': 'pa_Arab_PK',
                          'kmr': 'ku_TR',
                          'tl': 'en',
                          'iba': 'en',
                          'peo': 'fa',
                          }

# For the non-existing language manually overwrite the display language to make sure it is displayed correctly
CUSTOM_LANGUAGE_TRANSLATIONS = {'kmr': 'Kurdî (Tirkiye)',
                                'tl': 'ᜆᜄᜎᜓᜄ᜔',
                                'peo': 'Old Persian',
                                'iba': 'Iban',
                                'kab': 'Taqbaylit',
                                }

customize_babel_locale(CUSTOM_BABEL_LANGUAGES)

# This creates the lightbulb icons next to the adventure
KEYWORDS_ADVENTURES = {'print_command', 'ask_command', 'is_command', 'sleep_command', 'random_command',
                       'add_remove_command', 'quotation_marks', 'if_command', 'in_command', 'maths', 'repeat_command',
                       'repeat_command_2', 'for_command', 'and_or_command', 'while_command', 'elif_command',
                       'clear_command', 'pressit', 'debugging', 'functions'}


def adventures_order_per_level():
    if utils.is_redesign_enabled():
        return ADVENTURE_ORDER_PER_LEVEL
    else:
        return ADVENTURE_ORDER_PER_LEVEL_OLD


ADVENTURE_ORDER_PER_LEVEL_OLD = {
    1: [
        'print_command',
        'ask_command',
        'parrot',
        'rock',
        'haunted',
        'story',
        'music',
        'turtle',
        'turtle_draw_it',
        'restaurant',
        'fortune',
        'debugging',
        'hospital'
    ],
    2: [
        'is_command',
        'rock',
        'ask_command',
        'rock_2',
        'haunted',
        'sleep_command',
        'parrot',
        'story',
        'music',
        'restaurant',
        'turtle',
        'turtle_draw_it',
        'debugging',
        'hospital'
    ],
    3: [
        'random_command',
        'dice',
        'rock',
        'music',
        'fortune',
        'restaurant',
        'add_remove_command',
        'parrot',
        'dishes',
        'story',
        'haunted',
        'turtle',
        'turtle_draw_it',
        'debugging',
        'hospital'
    ],
    4: [
        'quotation_marks',
        'rock',
        'dice',
        'dishes',
        'parrot',
        'turtle',
        'turtle_draw_it',
        'clear_command',
        'music',
        'story',
        'haunted',
        'fortune',
        'restaurant',
        'debugging',
        'hospital'
    ],
    5: [
        'if_command',
        'music',
        'language',
        'dice',
        'dishes',
        'story',
        'rock',
        'parrot',
        'haunted',
        'turtle',
        'turtle_draw_it',
        'debugging',
        'hospital'
    ],
    6: [
        'elif_command',
        'story',
        'music',
        'in_command',
        'restaurant',
        'fortune',
        'pressit',
        'debugging',
        'hospital'
    ],
    7: [
        'maths',
        'is_command',
        'music',
        'songs',
        'dice',
        'dishes',
        'piggybank',
        'quizmaster',
        'turtle',
        'turtle_draw_it',
        'calculator',
        'fortune',
        'restaurant',
        'debugging',
        'hospital'
    ],
    8: [
        'repeat_command',
        'story',
        'years',
        'songs',
        'music',
        'dishes',
        'dice',
        'repeat_command_2',
        'fortune',
        'restaurant',
        'pressit',
        'turtle_draw_it',
        'debugging',
        'hospital',
        'congratulations'
    ],
    9: [
        'repeat_command',
        'fortune',
        'repeat_command_2',
        'songs',
        'music',
        'if_command',
        'story',
        'restaurant',
        'turtle',
        'turtle_draw_it',
        'debugging',
        'hospital'
    ],
    10: [
        'repeat_command',
        'if_command',
        'and_or_command',
        'rock',
        'story',
        'calculator',
        'calculator_2',
        'music',
        'secret',
        'restaurant',
        'haunted',
        'pressit',
        'turtle',
        'turtle_draw_it',
        'debugging',
        'hospital'
    ],
    11: [
        'for_command',
        'dishes',
        'dice',
        'fortune',
        'turtle',
        'turtle_draw_it',
        'harry_potter',
        'songs',
        'songs_2',
        'story',
        'rock',
        'calculator',
        'restaurant',
        'debugging',
        'hospital'
    ],
    12: [
        'functions',
        'music',
        'songs_2',
        'turtle',
        'turtle_draw_it',
        'debugging',
        'hospital',
        'congratulations'
    ],
    13: [
        'print_command',
        'is_command',
        'ask_command',
        'for_command',
        'story',
        'songs',
        'turtle_draw_it',
        'debugging',
        'hospital'
    ],
    14: [
        'random_command',
        'haunted',
        'songs',
        'language',
        'debugging',
        'hospital'
    ],
    15: [
        'functions',
        'songs',
        'piggybank',
        'music',
        'hotel',
        'calculator',
        'calculator_2',
        'turtle_draw_it',
        'debugging',
        'hospital'
    ],
    16: [
        'while_command',
        'story',
        'dice',
        'rock',
        'music',
        'turtle_draw_it',
        'calculator',
        'restaurant',
        'guess_my_number',
        'debugging',
        'hospital',
        'congratulations'
    ],
}


ADVENTURE_ORDER_PER_LEVEL = {level: [a for a in adventures if a not in ['parsons', 'quiz']]
                             for level, adventures in ADVENTURE_ORDER_PER_LEVEL_OLD.items()}

HOUR_OF_CODE_ADVENTURES = {
    1: [
        'print_command',
        'parrot',
        'turtle',
        'debugging',
        'hospital'
    ],
    2: [
        'is_command',
        'parrot',
        'turtle',
        'debugging',
        'hospital'
    ],
    3: [
        'parrot',
        'dishes',
        'turtle',
        'debugging',
        'hospital'
    ],
    4: [
        'dishes',
        'parrot',
        'story',
        'debugging',
        'hospital'
    ],
    5: [
        'language',
        'parrot',
        'turtle',
        'debugging',
        'hospital'
    ],
    6: [
        'story',
        'music',
        'fortune',
        'debugging',
        'hospital'
    ],
    7: [
        'maths',
        'song',
        'restaurant',
        'debugging',
        'hospital'
    ],
    8: [
        'songs',
        'restaurant',
        'turtle',
        'debugging',
        'hospital'
    ],
    9: [
        'songs',
        'story',
        'turtle_draw_it',
        'debugging',
        'hospital'

    ],
    10: [
        'rock',
        'restaurant',
        'turtle',
        'debugging',
        'hospital'
    ],
    11: [
        'songs',
        'dishes',
        'restaurant',
        'debugging',
        'hospital'
    ],
    12: [
        'functions',
        'music',
        'turtle',
        'debugging',
        'hospital'
    ],
    13: [
        'story',
        'songs',
        'hospital'
    ],
    14: [
        'haunted',
        'songs',
        'language',
        'debugging',
        'hospital'
    ],
    15: [
        'songs',
        'hotel',
        'calculator',
        'debugging',
        'hospital'
    ],
    16: [
        'dice',
        'music',
        'language',
        'guess_my_number',
        'debugging',
        'hospital'
    ]
}

# We must find our data relative to this .py file. This will give the
# correct answer both for when Hedy is run as a webserver on Heroku, as well
# as when it has been bundled using pyinstaller.
data_root = path.dirname(__file__)

content_dir = path.join(data_root, 'content')
translations_dir = path.join(data_root, 'translations')

RESEARCH = {}
for paper in sorted(os.listdir(f'{content_dir}/research'),
                    key=lambda x: int(x.split("_")[-1][:-4]),
                    reverse=True):
    # An_approach_to_describing_the_semantics_of_Hedy_2022.pdf -> 2022, An
    # approach to describing the semantics of Hedy
    name = paper.replace("_", " ").split(".")[0]
    name = name[-4:] + ". " + name[:-5]
    RESEARCH[name] = paper


# load all available languages in dict
# list_translations of babel does about the same, but without territories.
languages = {}
if not os.path.isdir(translations_dir):
    # should not be possible, but if it's moved someday, EN would still be working.
    ALL_LANGUAGES['en'] = 'English'
    ALL_KEYWORD_LANGUAGES['en'] = 'EN'

for folder in os.listdir(translations_dir):
    locale_dir = os.path.join(translations_dir, folder, 'LC_MESSAGES')
    if not os.path.isdir(locale_dir):
        continue
    if filter(lambda x: x.endswith('.mo'), os.listdir(locale_dir)):
        languages[folder] = CUSTOM_LANGUAGE_TRANSLATIONS.get(folder,
                                                             static_babel_content.LANGUAGE_NAMES.get(folder, folder))


for lang in sorted(languages):
    ALL_LANGUAGES[lang] = languages[lang]
    if os.path.exists(path.join(data_root, './grammars/keywords-' + lang + '.lark')):
        ALL_KEYWORD_LANGUAGES[lang] = lang[0:2].upper()  # first two characters

# Load and cache all keyword yamls
ALL_KEYWORDS = {
    str(lang): {
        str(k): str(v).split('|')
        for k, v in YamlFile.for_file(f'{content_dir}/keywords/{lang}.yaml').to_dict().items()
    }
    for lang in ALL_KEYWORD_LANGUAGES
}
KEYWORDS = {  # "default" keywords
    lang: {k: local_keys[0] for k, local_keys in keywords.items()}
    for lang, keywords in ALL_KEYWORDS.items()
}


class StructuredDataFile:
    """Base class for all data files in the content directory."""

    def __init__(self, filename):
        self.filename = filename
        self._file = None

    @property
    def file(self):
        """Lazily load the requested file."""
        if not self._file:
            self._file = YamlFile.for_file(self.filename)
        return self._file


class Commands(StructuredDataFile):
    def __init__(self, language):
        self.language = language
        super().__init__(f'{content_dir}/cheatsheets/{self.language}.yaml')

    def get_commands_for_level(self, level, keyword_lang):
        return deep_translate_keywords(self.file.get(int(level), {}), keyword_lang)


def deep_translate_keywords(yaml, keyword_language):
    """Recurse through a data structure and replace keyword placeholders in any strings we encounter.

    It's easy to make content that doesn't replace properly (make typos in the
    amounts of `{` for example), and then our entire build stops. So what we'll
    do instead is return the original, untranslated string. This makes it
    obvious that something is wrong if the content is looked at, while not
    stopping the build.
    """
    try:
        if isinstance(yaml, str):
            # this is used to localize adventures linked in slides (PR 3860)
            yaml = yaml.replace('/raw', f'/raw?keyword_language={keyword_language}')
            return safe_format(yaml, **KEYWORDS.get(keyword_language))
        if isinstance(yaml, list):
            return [deep_translate_keywords(e, keyword_language) for e in yaml]
        if isinstance(yaml, dict):
            return {k: deep_translate_keywords(v, keyword_language) for k, v in yaml.items()}
        return yaml
    except Exception as E:
        logger.exception(f'Issue in language {keyword_language}. Offending yaml: {yaml}. Error: {E}')
        return yaml


def try_render_keywords(str, keyword_language):
    """Render {placeholder}s in a string, return the original if there are any errors."""
    try:
        return safe_format(str, **KEYWORDS.get(keyword_language))
    except Exception:
        logger.exception('Error rendering keywords')
        return str


def get_localized_name(name, keyword_lang):
    return safe_format(name, **KEYWORDS.get(keyword_lang))

# Todo TB -> We don't need these anymore as we guarantee with Weblate that
# each language file is there


class NoSuchCommand:
    def get_commands_for_level(self, level, keyword_lang):
        return {}


class Adventures(StructuredDataFile):
    def __init__(self, language):
        self.language = language
        super().__init__(f'{content_dir}/adventures/{self.language}.yaml')

    def get_adventure_keyname_name_levels(self):
        return {aid: {adv['name']: list(adv['levels'].keys())} for aid, adv in self.file.get('adventures', {}).items()}

    def get_sorted_level_programs(self, programs, adventure_names):
        programs_by_level = []
        for item in programs:
            programs_by_level.append(
                {'level': item['level'],
                 'adventure_name': item.get('adventure_name', item['name']),
                 }
            )

        sort = {}

        for program in programs_by_level:
            if program['level'] in sort:
                sort[program['level']].append(adventure_names.get(program['adventure_name'], program['adventure_name']))
            else:
                sort[program['level']] = [adventure_names.get(program['adventure_name'], program['adventure_name'])]
        for level, adventures in sort.copy().items():
            sort[level] = sorted(adventures, key=lambda s: s.lower() if s else "")

        return dict(sorted(sort.items(), key=lambda item: item[0]))

    def get_sorted_adventure_programs(self, programs, adventure_names):
        programs_by_adventure = []
        for item in programs:
            if item.get('adventure_name'):
                programs_by_adventure.append(
                    {'adventure_name': adventure_names.get(item.get('adventure_name')) or item.get('adventure_name'),
                     'level': item['level'],
                     }
                )

        sort = {}
        for program in programs_by_adventure:
            if program['adventure_name'] in sort:
                sort[program['adventure_name']].append(program['level'])
            else:
                sort[program['adventure_name']] = [program['level']]
        for adventure, levels in sort.copy().items():
            sort[adventure] = sorted(levels, key=lambda item: item)

        return {key: sort[key]
                for key in sorted(sort.keys(), key=lambda s: s.lower() if s else "")}

    def get_adventure_names(self, keyword_lang):
        return {aid: adv['name'] for aid, adv in deep_translate_keywords(
            self.file.get('adventures'), keyword_lang).items()}

    def get_adventures(self, keyword_lang="en"):
        return deep_translate_keywords(self.file.get('adventures'), keyword_lang)

    def get_adventures_subset(self, subset=["print_command", "parrot"], keyword_lang="en"):
        adventures = {aid: adv for aid, adv in self.file.get('adventures', {}).items() if aid in subset}
        return deep_translate_keywords(adventures, keyword_lang)

    def has_adventures(self):
        return True if self.file.get('adventures') else False


class NoSuchAdventure:
    def get_adventure(self):
        return {}


class Slides(StructuredDataFile):
    def __init__(self, language):
        self.language = language
        super().__init__(f'{content_dir}/slides/{self.language}.yaml')

    def get_slides_for_level(self, level, keyword_lang="en"):
        return deep_translate_keywords(self.file.get('levels', {}).get(level), keyword_lang)


class NoSuchSlides:
    def get_slides_for_level(self, level, keyword_lang):
        return {}


class NoSuchWorkbooks:
    def get_workbook_for_level(self, level, keyword_lang):
        return {}


class Workbooks(StructuredDataFile, NoSuchWorkbooks):
    def __init__(self, language):
        self.language = language
        super().__init__(f'{content_dir}/workbooks/{self.language}.yaml')

    def get_workbook_for_level(self, level, keyword_lang='en'):
        workbook_for_level = self.file.get('levels', {}).get(level, {})
        return deep_translate_keywords(workbook_for_level, keyword_lang)


class NoSuchTags:
    def get_tags(self):
        return {}


class Tags(StructuredDataFile):
    def __init__(self, language):
        self.language = language
        super().__init__(f'{content_dir}/tags/{self.language}.yaml')

    def get_tags_names(self):
        return {tid: tags['items'] for tid, tags in self.file.get('tags', {}).items()}

    def get_tags(self, keyword_lang="en"):
        return deep_translate_keywords(self.file.get('tags'), keyword_lang)

    def has_tags(self):
        return True if self.file.get('tags') else False
